package com.hjx.movie.core.security.auth;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hjx.movie.common.utils.LoginType;
import com.hjx.movie.common.utils.ResultType;
import com.hjx.movie.common.utils.exceptions.CustomException;
import com.hjx.movie.core.security.service.IAuthService;
import com.hjx.movie.core.security.util.JwtAuthTokenUtil;
import com.hjx.movie.core.system.service.admin.IAccountService;
import com.hjx.movie.core.system.service.admin.IRoleService;
import com.hjx.movie.core.system.service.admin.IUserRoleService;
import com.hjx.movie.core.system.service.admin.IUserService;
import com.hjx.movie.core.system.service.commons.mail.MailService;
import com.hjx.movie.core.system.vo.MailMsgVO;
import com.hjx.movie.core.util.PasswordUtil;
import com.hjx.movie.persistence.system.model.IvyAccount;
import com.hjx.movie.persistence.system.model.IvyRole;
import com.hjx.movie.persistence.system.model.IvyUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * <p>Title:</p>
 * <p>Description:</p>
 *
 * @author hjx
 * @date 2020/5/27 - 20:45
 */
@Slf4j
@Service
@Transactional
public class JwtAuthService {

    @Resource
    AuthenticationManager authenticationManager;
    @Resource
    private JwtAuthTokenUtil jwtAuthTokenUtil;
    @Resource
    RedisTemplate<String,String> redisTemplate;
    @Resource
    private IAuthService authService;
    @Resource
    private IUserService userService;
    @Resource
    private MailService mailService;
    @Resource
    private IAccountService accountService;
    @Resource
    private IRoleService roleService;
    @Resource
    private IUserRoleService userRoleService;
    /**
     * 登录认证换取JWT令牌
     *
     * @return token
     */
    public String login(String username, String password,Integer loginType) throws AuthenticationException {
        //无论传进来的username是帐号 邮箱还是手机 这里统一查询出uid
        String uid = authService.findUserId(username);
        log.debug("查询出来的UID为:{}", uid);
        if (uid == null || StringUtils.isBlank(uid)) {
            throw new UsernameNotFoundException("该用户名不存在");
        }
        if (loginType!= LoginType.USERNAME_LOGIN.getTypeCode()){
            return captchaLogin(uid,username,password);
        }else {
            return usernameLogin(uid,password);
        }
    }

    public String fastRegisterAndLogin(String email,String captcha){
        String code = redisTemplate.opsForValue().get(MailService.getMailKey(email));
        if (StringUtils.isNotBlank(code)&&code.equals(captcha)){
            List<IvyUser> users = userService.list(new QueryWrapper<IvyUser>().eq("email",email));
            log.debug("根据邮箱查询的结果为:{}",users);
            IvyUser user;
            String password = PasswordUtil.generateRandomPassword();
            //判读数据库中是否存在该用户
            if (users.isEmpty()){
                log.debug("未在数据库中查询到该邮箱,将进行用户新增");
                String username = "user_"+System.currentTimeMillis();
                user = IvyUser.builder()
                        .username(username)
                        .password(password)
                        .email(email).headPic("default/userImg/pdx.gif")
                        .nickname(username).status(true).gender("2")
                        .build();
                userService.insertUser(user);
                // 新增用户之后 新增账户
                accountService.save(IvyAccount.builder()
                        .userId(user.getUserId()).status(true).balance(new BigDecimal(0))
                        .build());
                //新增角色
                IvyRole roleUser = roleService.queryRoleLike("ROLE_user").get(0);
                userRoleService.addOrDeleteUserRole(user.getUserId(), Collections.singleton(roleUser.getRoleId()));
                log.debug("用户新增成功,{}",user);
                mailService.sendMessageMail(MailMsgVO.builder().recipient(email)
                        .subject("iMovie快速注册信息")
                        .html(false).content("邮箱用户"+email+"你好！" +
                                "iMovie快速登录随机生成的帐号为："+username+" 密码为:"+password)
                        .build());
            }else {
                user = users.get(0);
                log.debug("根据邮箱查询到用户,{}",user);
            }
            Assert.notNull(user,"快速登录失败");

            String uid = user.getUserId().toString();
            String token = jwtAuthTokenUtil.createToken(uid);
            String cacheName = jwtAuthTokenUtil.getCacheTokenName(uid);
            //将生成的token存入缓存
            redisTemplate.opsForValue().set(cacheName,token,jwtAuthTokenUtil.getExpiration(),TimeUnit.HOURS);
            //删除这次成功登陆的验证码
            redisTemplate.delete(MailService.getMailKey(user.getEmail()));
            return token;
        }else {
            throw new CustomException(ResultType.USER_INPUT_ERROR,"输入的验证码错误");
        }
    }

    public String usernameLogin(String uid,String password){
        //用uid和password来获取认证令牌 此时的token还是未认证的
        UsernamePasswordAuthenticationToken upToken =
                new UsernamePasswordAuthenticationToken(uid, password);
        //一般的流程 UsernamePasswordAuthenticationFilter
        //登录认证 在这会调用UserDetailsService中的loadUserByUsername()方法获取信息 然后进行密码的验证

        //实际是由ProviderManager一个一个的寻找可以提供认证的提供者
        //UsernamePasswordAuthenticationToken 的认证提供者是AbstractUserDetailsAuthenticationProvider这个抽象类
        //这个抽象类的实现类为DaoAuthenticationProvider 实际调用UserDetailsService中的loadUserByUsername()方法就是在这里进行的
        //密码的验证也是DaoAuthenticationProvider来进行验证的
        Authentication authenticate = authenticationManager.authenticate(upToken);
        User user = (User) authenticate.getPrincipal();
        String token = jwtAuthTokenUtil.createToken(user.getUsername());
        String cacheName = jwtAuthTokenUtil.getCacheTokenName(uid);
        //将生成的token存入缓存
        redisTemplate.opsForValue().set(cacheName,token,jwtAuthTokenUtil.getExpiration(), TimeUnit.HOURS);
        log.debug("用户登录成功,Token信息存入缓存========>{}:{}",cacheName,token);
        return token;
    }

    public String captchaLogin(String uid,String username, String captcha){
        String code = redisTemplate.opsForValue().get(MailService.getMailKey(username));
        if (StringUtils.isNotBlank(code)&&code.equals(captcha)){
            String token = jwtAuthTokenUtil.createToken(uid);
            String cacheName = jwtAuthTokenUtil.getCacheTokenName(uid);
            //将生成的token存入缓存
            redisTemplate.opsForValue().set(cacheName,token,jwtAuthTokenUtil.getExpiration(),TimeUnit.HOURS);
            //删除这次成功登陆的验证码
            redisTemplate.delete(MailService.getMailKey(username));
            return token;
        }else {
            throw new CustomException(ResultType.USER_INPUT_ERROR,"输入的验证码错误");
        }
    }


    public boolean logout(String uid){
        Boolean result = null;
        if(StringUtils.isNotBlank(uid)){
            log.debug("用户登出,即将删除UID为{}的Token缓存",uid);
            result = redisTemplate.delete(jwtAuthTokenUtil.getCacheTokenName(uid));
        }
        return result != null && result;
    }

    public boolean checkToken(String token) {
        //不过期则刷新token
        if (StringUtils.isNotBlank(token)&&!jwtAuthTokenUtil.isTokenExpired(token)) {
            log.debug("Token未过期，开始确认Token信息");
            String uid = jwtAuthTokenUtil.getUidFromToken(token);
            String cacheToken = redisTemplate.opsForValue().get(jwtAuthTokenUtil.getCacheTokenName(uid));
            return cacheToken != null && cacheToken.equals(token);
        }
        log.debug("该Token已过期");
        //过期就返回null
        return false;
    }


    public String refreshToken(String oldToken) {
        //不过期则刷新token
        if (StringUtils.isNotBlank(oldToken)&&!jwtAuthTokenUtil.isTokenExpired(oldToken)) {
            log.debug("Token未过期，开始刷新Token");
            String uid = jwtAuthTokenUtil.getUidFromToken(oldToken);
            String newToken = jwtAuthTokenUtil.refreshToken(oldToken);
            log.debug("刷新缓存里UID为{}的Token，新Token为:{}",uid,newToken);
            redisTemplate.opsForValue().set(jwtAuthTokenUtil.getCacheTokenName(uid),newToken);
            return newToken;
        }
        log.debug("刷新Token失败,该Token已过期");
        //过期就返回null
        return null;
    }
}
